美坂 †
前言 †原文:http://lovelove.rabi-en-rose.net/misaka/specification.html 撰文與翻譯:rexboy 美坂官方:http://lovelove.rabi-en-rose.net/index_misaka.php 這篇文章,是參照美坂本家規格書的架構跟原文,再加入一些補充的東西寫成的。 不過,畢竟這兒只是介紹美坂的基本語法,沒有整體提到Ghost的具體架構。所以各位可以搭配美坂所附的Ghost範本,會更容易上手些。 基本定義檔 †misaka.ini是美坂的重要設定檔。內容大概長得像這個樣子: dictionaries { misaka.txt hoge.txt datamisaka.txt } debug,0 debugsaori,0 error,0 propertyhandler,0 dictionarie之下的括弧內,必須舉出Ghost會使用到的辭書檔。 辭書檔裡面,就是我們要費心撰寫的Ghost資料了。包含人物的對話,選單,發生事件時該有的反應..等等,都是寫在辭書檔裡的。 只要把該有的對話,事件都做出來了,Ghost就可以正常運作。內容可以全部擠在同一個辭書檔內,也可以分成很多個。看個人的習慣而定。 debug決定是否要建立除錯紀錄檔。以bool值指定(0=否,1=是)。除錯檔名為misaka_debug.txt,位在 misaka.dll的同一個目錄下。美坂會把動作期間所有執行的步驟輸出到這個檔案裡面。所以當美坂突然結束或是當掉的時候,可以從這個檔案裡找出問題 所在。檔案本身膨脹相當快,請小心使用。預設值為0。 debugsaori決定是否要建立saori的除錯紀錄檔。以bool值指定。關於Saori這裡先暫時略過。除錯檔名為 misaka_debugsaori.txt,位於misaka主要DLL的同一個目錄下。紀錄對saori提出的request和接收到的 response內容。預設值為0。 美坂在讀入的時候,有所謂的前置處理階段。error決定是否要在此時掃瞄一遍辭書檔的語法錯誤,並且將錯誤的部分整行輸出成misaka_error.txt。也是供除錯使用。以bool值指定,位於misaka主要DLL的同一個目錄下。預設值為0。 單純變數 †美坂可以在任何時候使用新的變數,變數的型態由使用時決定。 關於變數有一些注意點: 1. 某些情形下,文字可以直接被當成數值處理。不過當其以數值處理,卻又無法正確轉換時,會被當成0來看待。 2. bool值可以用true跟false保留字來處理。 3. 美坂只能處理正負整數,無法處理小數。 4. 字串必須以雙引號括起來。 變數值的代入可以使用下面的語法。代入時如果變數不存在,則產生新的變數。 單純代入 †單純代入使用=運算子連結。例如: {$z=128} {$nyo="喵~"} 記得字串的前後一定要用雙引號括起來。 演算代入 †我們也可以把計算結果代入變數。例如: {$z=(1+1)*4} 在這裡就可以看出雙引號的重要性了。像下面的寫法,美坂就不會計算右邊式子的結果,而只把它當成普通的文字: {$z="(1+1)*4"} 單純演算代入 †這是簡化的寫法,對使用過某些程式語言的人或許很熟悉: {$z++} {$z--} {$z+=n} {$z-=n} {$z*=n} {$z/=n} 從上往下,依序是$z的值加一,$z的值減一,把$z的值加上n,把$z的值減掉n,把$z的值乘上n,以及把$z的值除以n。 評價(取值) 要取變數的值,可以使用下面的語法。 {$z} 可以在任何時候使用,也可以「巢狀」取值。例如: {$z=({$a}+1)*{$b}} 有一些實用的系統函數可以用來操作變數。請參考〔系統函數〕的部分。 單純變數的值在結束時會被自動保存,並且在下次啟動時回復。 陣列變數 †陣列變數也是可以自由定義的。除了內含有多個元素以外,基本上跟單純變數沒有太大差別。 在宣告階段,單純變數跟陣列變數的表示法沒有什麼不同。例如: {$list=""} 是指定空的$list陣列變數。 要增加陣列變數的元素,可以使用append這個函數。 {$append($list,"あいうえお")} {$append($list,"かきくけこ")} 當我們呼叫陣列變數的時候,如果不作任何指定,則傳回的值會從陣列中隨機選取。例如,經過上面兩行處理後,如果我們用{$list}取值的話,傳回的可能會是「あいうえお」,也可能會是「かきくけこ」。 如果要取用陣列中特定的元素,則必須使用[]符號。 {$list[1]} 這樣就一定會傳回「かきくけこ」了。請注意,陣列元素是從0開始的。 陣列的最大元素個數不限。有一些實用的系統函數可以用來使用陣列變數。請參考〔系統函數〕的部分。 陣列變數同樣會在結束時自動保存,再啟動時重新讀入。 辭書變數 †辭書變數會在啟動時自動被讀入。辭書變數的構造類似陣列變數,不過增加了一些特殊性質: 1. 唯讀。 2. 可以有「採用條件式」。 其餘的部分大致跟陣列變數相同。 辭書變數也可以使用系統函數來控制。不過,此時辭書變數會被當成普通的陣列變數看待,暫時失去部分的特性。 辭書變數的採用條件式中,可以包含的設定引數 辭書變數可以包含多個設定引數。這是和一般變數不同的地方。 邏輯運算式 †含有邏輯運算式的辭書變數,就像是「符合條件才能使用的變數」一樣。這可以讓頻繁發生事件,有更多樣化的反應。寫法像以下這樣: $OnBoot; {$if ({$hour}==12)}; \s\0起動、12時。\e $OnBoot \s\0それ以外。\e 像這樣,當時間是12點的時候,就會採用上面的辭書變數﹔其他情況,則採用下面的。利用這種特性,就可以簡單地讓同一個事件,在不同情況下,產生各式各樣的反應。 先被定義的辭書變數,會先被判斷。(距離檔案開頭比較近的﹔如果在不同檔案中的話,則以misaka.ini所引入的順序判定。)當結果為真,就直 接取值傳回,不再繼續往下判斷。利用這種特性,就可以輕易的寫出出if-elseif-else的架構了。(因此,沒有條件式的辭書變數,必須擺在同類變 數的最後一個。否則在它之下的同類辭書變數將永遠不會被呼叫到。) 當判斷引數是邏輯運算式的時候,不一定要使用$if結構。像下面的寫法也是正確的: $Dummy; {$stringexists({$array},{$s})}; \s\0既にある。\e $Dummy \s\0ない。\e nonoverlap †被指定nonoverlap的辭書變數,就不會重複傳回已經使用過的值。直到其中所有的元素都已經使用過一輪之後,才會再重複出現。這個性質通常使用於RandomTalk之中,防止重複的話題一直出現。例如: $_OnTalkCore; nonoverlap; A B C D E F $_OnTalkCore在使用的時候和一般的辭書變數沒有什麼不同。不過當我們連續呼叫它的時候,得到的結果可能會像是「A」「D」「B」「F」「E」「C」,或是「C」「F」「A」「D」「E」「B」,總之,在全部都使用過之前,它絕對不會重複出現。 sequential †被指定sqeuential的辭書變數,值會從第零個元素開始依序傳回。等全部的元素都傳回過之後,又回到第0個元素。例如 $_alphabet; sequential; A B C D E F 則連續呼叫$_alphabet得到的值會是A,B,C,D,E,F,A,B,C,D,E,F......。 所有的引數都可以重複使用。邏輯運算式跟nonoverlap等等也可以同時指定。舉一個重複指定的例子: $OnBoot; {$if ({$hour}==12)}; nonoverlap; 不過,有些引數、邏輯運算式的組合會造成無效的情形。像是nonoverlap跟sequential兩個引數,因為動作內容重複,無法同時使用。 前置處理符號 †#_Common †#_Common裡面所宣告的邏輯運算式,會和該檔案內所有的Symbol,以邏輯運算子&&連接起來。也就是說,只有在#_Common中設定的條件成立時,該辭書檔內的Symbol才會生效。通常對於含有多種模式的Ghost很有用。所以,我們不需要在檔案中寫好幾次的模式判定,只要把檔案分開就可以做到了。例如: #_Common {#if ({$mode}==0)} 則該檔案內的所有Symbol都會被加上邏輯運算式{$if ({$mode}==0)}。 系統Symbol †一般以On開頭的事件處理Symbol會自動成為系統Symbol。像是$OnBoot,$OnGhostChange等等。為了方便使用,省略事件名稱以外的部分。 $OnNotify_* †當NOTIFY request產生的時候,美坂首先會將 Reference 參數適當的轉成系統變數,經由核心作最低限度的處理之後,會變成持有 $OnNotify_id Symbol的原始碼來執行。Reference參數在接受NOTIFY的情況下是會持續存在的。另外就NOTIFY request的性質而言,它並不會傳回任何的 Script ,而會直接被廢棄掉。 $_Variable †$_OnVariable會在美坂起動之後、使用者變數自動還原之前被呼叫。主要是用於設定全部變數的初值。不 過這只在初次啟動時才有效。我們可以把它看待成全域變數的宣告區。雖然在美坂中,所有的變數都是不需要經過宣告的Variant(變異數)型態,不過我們 可以像這樣先把變數訂出來,以增加整個辭書檔的可讀性。當然,這是選擇性的。 $_Constant †$_OnConstant會在美坂起動之後、使用者變數自動還原之後被呼叫。換句話說,它會無視於自動還原,強制設定變數的初值。可以當成全域的常數宣告區來看待。 $_OnGhostCommunicateReceive †接收到其他Ghost送來的對話時,就會被呼叫。要知道送來的訊息來自誰,或是有沒有說了哪些話的話,可以參考系統變數{$sender}和系統函數{$insentence},{$inlastsentence}。 $_OnRandomTalk †每經過一段時間,它會被定時呼叫。間隔時間是系統變數$_talkinterval指定的0.5到1.5倍。一般被稱為"Random Talk"。 系統變數(Passive) †下面的系統變數是由系統預先定義好的,並且一直保持自動更新。使用者不能再定義同名的環境變數。 $year / $month / $day / $hour / $minute / $second / $dayofweek †現在的年/月/日/時/分/秒/星期,星期從0到6依序代表星期日到星期六。 $elapsedhour / $elapsedminute / $elapsedsecond †Ghost的連續啟動時間。依序是時/分/秒。 $elapsedhouros / $elapsedminuteos / $elapsedsecondos †作業系統的連續啟動時間。依序是時/分/秒。 $elapsedhourtotal / $elapsedminutetotal / $elapsedsecondtotal †Ghost的總共啟動時間。依序是時/分/秒。 $os.version †作業系統版本。「5.0.2195」等等。 $os.name †作業系統一般的名稱。「Windows 2000」等等。 $os.phisicalmemorysize †實體記憶體總容量。單位byte。 $os.freememorysize †空的實體記憶體容量。單位byte。 $os.totalmemorysize †含虛擬記憶體的總記憶體容量。單位byte。 $cpu.vendorname †目前使用中的CPU廠牌名稱。「Intel」等等。 $cpu.name †目前使用中CPU一般的名稱。「Athlon」「PentiumIII」等等。 $cpu.clockcycle †目前使用中CPU的時脈。單位MHz。 $daysfromlastupdate †距離上一次線上更新的日數。單位為天。例如: $_OnBoot; {$if (7<={$daysfromlastupdate})}; \s\0そろそろネットワーク更新して欲しい。\e $_OnBoot \s\0通常。\e 像這樣,就可以用來催促使用者經過七天就更新。 $daysfromfirstboot †第一此啟動後經過的日數。單位為天。 $lastsentence †最近一次COMMUNICATE時,由對方傳來的Script。也是$inlastsentence函式要比較的對象。 $otherghostlist †陣列變數。同一個桌面上存在的其他Ghost的列表。在只有自己的情況下是空值(NULL)。 $hwnd.sakura / $hwnd.kero / $hwnd.sakuraballoon / $hwnd.keroballoon †各自的HWnd值。 系統變數(Active) †下面的系統變數是由系統預先定義好的,作為某些特殊用途。使用者不能再定義同名的環境變數。 $to †Ghost間COMMUNICATE時,要傳送到的Ghost名稱。設定這個變數後,發言就會傳送到所指定的Ghost身上。例如 {$to=花ちゃん}s0こんにちは。e $to在每次說話之後都會重新設定。 $_talkinterval †系統Symbol $_OnRandomTalk 的呼叫間隔。單位秒。 系統函數 †下面的系統函數名稱是由系統預先定義好的,它們永遠會被判斷為函數。使用者不能再定義同名的變數。 評價函數 †{$if [ifelement] { [true] } else {false}} †if 結構。在判斷邏輯算式[ifelement]的bool值後,傳回{true}或{false}大括弧中的內容。例如: \s\0{$if (({$month}==12) && (({$day}==24) || ({$day}==25))) { クリスマス } else { ただの日 }}\e 如果現在時間是12月24日或25日的話,就會得到「聖誕節」﹔否則得到「其他」。 關係運算子可以使用 == <= >= < > != 邏輯運算子可以使用 && || 處理true值跟處理false值的兩個部分都是可以省略的。當我們只省略else {false}的部分的時候,如果[ifelement]的值為false,則什麼東西都不會傳回,就像完全消失了一樣。不過,當{true}的部分也跟 著一起省略的時候,$if會傳回[ifelement]的bool值。下面的例子全部都是正確的: {$if (({$hour}==9) || ({$minute}==59))}にょーん。e {$if (({$hour}==9) || ({$minute}==59)) { 不細工{$hour} } else { 不細工じゃない }}にょーん。e {$if (({$hour}==9) || ({$minute}==59)) { 不細工{$hour} }}にょーん。e {$if (({$hour}==9) || ({$minute}==59)) { 不細工{$if ({$hour}==9)} }}にょーん。e 下面有幾個注意事項: 每個式子必須以完全獨立的括弧括起來。 {$if ({$month}==12 && ({$day}==24 || {$day}==25))} 雖然在C語言中,這樣寫是可以被編譯器接受的,不過美坂不接受這種寫法。應該改成: {$if (({$month}==12) && (({$day}==24) || ({$day}==25)))} 不過,當使用相同的關係運算子的時候,還是可以把多個邏輯算式分配在同一個括號裡面。像下面這樣: {$if ({$month}==12 && {$day}==24 && {$hour}==0)} 空格只能增加不能省略。當空格不夠的時候,可能會造成美坂失控暴走。下面是錯誤示範: {$if(({$month}==12) && (({$day}==24) || ({$day}==25))) {聖誕節} else {其他}} 基本函數 †{$reference(n)} †傳回目前編號n的reference內容。 {$random(n)} †傳回0~n-1之間的亂數(值為整數)。 {$calc(expression)} †傳回expression表示的四則運算式的結果。算式可以有很多項,也可以使用括弧。例如: {$calc(1+1)} {$calc(1+1*2)} {$calc((1+1)*2)} {$a=1}{$b=2}{$c={$calc({$a}*5+{$b}*10)}}{$c}点。 可以使用的運算子 + 加法 - 減法 * 乘法 / 除法 % 取餘數 ^ 次方 同樣不能計算小數點。除法的結果小數點以下無條件捨去。 字串函數 †{$extractfilename(s)} †字串s會被當成檔案名稱,傳回去除路徑後的檔名。例如: C:hogehoge.txt → hoge.txt {$index(n,s)} †在字串s裡面尋找跟字串n完全符合的部分,並且傳回找到的開頭位置。單位byte。例如: {$index("hum","human")} {$index("hux","human")} {$index("n","human")} {$insentence(s,n,n,n,n....)} †比對字串s跟後面所有的n是否都有相符合的部分,傳回其bool值。引數個數不限。 因為引數的順序跟index函數相反,使用時請注意。 {$insentence("human","hum")} {$insentence("human","hux")} {$insentence("human","hum","uma","u")} {$insentence("human","hum","hua")} {$inlastsentence(n,n,n,n....)} †類似insentence的函數。比對lastsentence跟所有的n是否都有相符合的部分,傳回其bool值。 {$length(s)} †傳回字串s的長度(byte數)。 {$substring(s,offset,count)} †{$substringl(s,count)} †{$substringr(s,count)} †傳回字串s的一部分: {$substringw(s,m,n)} †{$substringwl(s,n)} †{$substringwr(s,n)} †寬字元版的substring。字元數是由實際的文字數計算,而非byte。其餘皆和substring相同。 {$temp="あaいbうcえdお"}{$substringl({$temp},3)} {$temp="あaいbうcえdお"}{$substringlw({$temp},3)} 上式的結果是「あa」、下式的結果是「あaい」。 {$substringfirst(s)} †{$substringlast(s)} †類似substringw的函數。substringfirst是取s的前面一個字,而substringlast是取s的最後一個字。函數考慮MBCS(由實際的文字數計算,而非byte)。 {$hiraganacase(s)} †日語專用的函式。將字串s中所有的平假名轉成片假名。漢字等不能轉換的部分會被保留。當然這無法適用於big5日文..(汗) {$isequallastandfirst(s0,s1)} †從\s\0的末端和s1的開頭,各取一個字出來比較,傳回兩者是否相同的bool值。函數考慮MBCS(由實際的文字數計算,而非byte)。 {$getvalue(n,m)} †依照字串n中的逗號,將n分割成數個元素,並傳回其中的第n個。例如: {$_ref="a,b,c,d,e,f"}{$getvalue("{$_ref}",3)} 這個式子的值是d。 {$getvalueex(n,m)} †依照字串n中單位元的字,將n分割成數個元素,並傳回其中的第n個。 陣列函數 †{$append(a,s)} †新增一個元素s到陣列變數a裡面。當a只是單純變數的時候,會被自動轉換成陣列變數。 {$copy(a0,a1)} †把陣列變數a0的值複製到a1中。a0也可以是辭書變數,不過複製時,採用條件式會被忽略。且先找到的辭書變數先採用。 {$count(a)} †傳回陣列 a的元素個數。當a是單純變數時,傳回值恆為1。當a為陣列中的元素,或是其值為NULL時,則傳回-1。 {$pop(a)} †隨機傳回陣列變數a中的一個元素,並且將該元素從陣列中刪除。陣列為空時則傳回空字串。 {$popmatchl(a,s)} †隨機傳回陣列變數a中,其左方和s相同的元素。並且將該元素從陣列中刪除。沒有相同元素時則傳回空字串。 {$stringexists(a,s)} †尋找陣列變數a中,是否有跟s完全相同的字串元素。傳回其bool值。 SAORI函數 †$loadsaori(filename) †讀入filename所指定的SAORI DLL並鎖定。 $unloadsaori(filename) †關閉filename所指定的SAORI DLL,並且解除鎖定。 $saori(filename,arg0,arg1,arg2....) †執行filename所指定的SAORI,並且以arg為argument header傳入。直到SAORI傳回response之前,都不會繼續往下動作。讀取不到指定的DLL時,或者SAORI內部發生錯誤時中止。例如: {$loadsaori("substr.dll")} {$temp={$saori("substr.dll","あいうえお",2,3)}}{$temp} {$unloadsaori("substr.dll")} 假設這是里々內附的substr.dll。$temp的值會被設為「いうえ」。 上面的例子是在讀取完後立刻執行,執行完就立刻關閉。不過,並沒有特別限制一定要這樣做。 在美坂起動的同時讀取(例如$_Constant等),一直存放在記憶體中,隨時呼叫也是可以的。 存活期由使用者自由決定。 一般來說,在美坂啟動的同時,讀入全部要用到的SAORI,也不會有什麼問題。這樣做有時可以省去一些麻煩。 另外,在美坂結束的時候,也會自動關閉所有的SAORI。所以不需要太擔心。 其他的函數 $choice(n,n,n,n,n,n....) †隨機傳回一個引數。可以使用的引數個數不限。例如: {$choice("あいうえお",{$b},256)} $isghostexists(n) †判斷字串n指定的Ghost是否在同一個桌面上。傳回bool值。 $search(n,n,n,n....) †尋找n所指定的元件名稱,並隨機選取一個,取其值傳回。例如: $_OnTest {$search("human","japan")} $_OnTest2 {$search("male")} $human-male-japan A $human-female-japan B $human-male-korea C $human-female-korea D 則$_OnTest會傳回A或B,$_OnTest2會傳回A或C。 $backup() †沒有引數。用來備分美坂在記憶體裡全部的資料。動作內容相同於美坂結束時的自動變數保存。 $getmousemovecount(n,m) †傳回角色n(0=Sakura,1=Unyu)的m部位的OnMouseMove次數。通常用來控制「撫摸反應」的敏感度。例如: $OnMouseMove,{$if (64<={$getmousemovecount(0,"Bust")})} {$resetmovecount(0,"Bust")}\s\0被摸胸部的反應。\e $OnMouseMove,{$if (32<={$getmousemovecount(0,"Bust")})} \s\0懷疑被摸胸部的反應。\e 附註:部位的名稱是由Shell指定的。請參考製作Shell的相關文件。 resetmousemovecount(n,m) †重設角色n(0=Sakura,1=Unyu)的m部位的OnMouseMove次數。 註解 †在一行的開頭加上//,則整行的內容會被視為註解,內容完全忽略。 暗號化(編碼) †執行misakac.exe可以把同一個目錄下的 *.txt 檔案全部暗號化,輸出成 *.__1。 .__1不再像一般的文字檔案,能輕易觀看到其原始內容,不過它還是可以當成普通的辭書檔,在misaka.ini中引入。 這個功能,通常是用來保密辭書檔的原始內容。當然,也無法完全保證不會被別人看到。 因為misakac.exe會對同目錄下所有 .txt的檔案動作,所以可能會一併把readme.txt等等無關的檔案暗號化。雖然原始檔案會被保留,不過操作時請稍微留意一下。 SAORI 請參考系統函數的SAORI函數部分。 關於SAORI的詳細資料,這裡暫時省略。 全體的傾向,注意事項,小常識等 要區分大小寫 例如,$z和$Z會被視為不同的變數來處理。 資料必須全部為Shift_JIS 所有的雙位元文字必須使用Shift_JIS碼。 不過目前使用Big5碼來撰寫還沒有發現嚴重的問題。 從0開始計數 大部分的序數資料由0開始計數。 字串必須以雙括號括住 例如: {$_ref="a,b,c,d,e,f"} 不可以這樣寫: {$_ref=a,b,c,d,e,f} 寫法錯誤的時候會關閉 錯誤發生時通常會出現錯誤訊息,然後直接關閉。期間無法做任何其他動作。 大括弧{}有「評價(取值)」的意思 例如我們寫$hour,它就只會單單印出$hour這一串字而已。只有寫成{$hour}的時候,它才會取 $hour 的值並且傳回。 {$lasthour="$hour"} {$lasthour={$hour}} 在上面那一行,$lasthour只會變成字串﹔而在下面那一行,$lasthour會變成$hour的值。 也有些函數要傳遞變數本身才能運作,此時就不需要用大括號取值。例如使用$append增加陣列元素的時候: {$append($z,"あいうえお")} 就不能寫成下面這樣: {$append({$z},"あいうえお")} 因為這個時候需要用變數本身來處理,而不是它的值。 用來指示區塊開始/結束的大括弧必須自成一行。 例如,這是錯誤的寫法: $_OnTest { \0\s0\1\s0 \0ねえ。\w8\w8 \1\s0ん?\w8\w8 \0\n\n‥‥\w8\w8いや、いい。\w8\w8 \1\n\n‥‥\w8\w8何だよ。 \e } 應該改成這樣: $_OnTest { \0\s0\1\s0 \0ねえ。\w8\w8 \1\s0ん?\w8\w8 \0\n\n‥‥\w8\w8いや、いい。\w8\w8 \1\n\n‥‥\w8\w8何だよ。 \e } |